package org.ihelpuoo.weboo.module;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.Vector;

import javax.servlet.ServletContext;

import org.apache.log4j.Logger;
import org.ihelpuoo.lang.ListUtils;
import org.ihelpuoo.lang.StringUtils;
import org.ihelpuoo.localize.LocaleComparator;
import org.ihelpuoo.util.Properties;
import org.ihelpuoo.weboo.Weboo;
import org.ihelpuoo.weboo.bean.ControllerBean;
import org.ihelpuoo.weboo.exception.InvalidModuleNameException;
import org.ihelpuoo.weboo.exception.ModuleNotFoundException;
import org.ihelpuoo.weboo.exception.ModuleOverlayException;
import org.ihelpuoo.weboo.exception.ModuleUninstallException;

public class ModuleManager
{
	private Logger	log	= Logger.getLogger(ModuleManager.class);

	public static ModuleManager me(ServletContext sc)
	{
		ModuleManager mm = (ModuleManager)sc.getAttribute(ModuleManager.class.getName());
		if (null == mm)
		{
			mm = new ModuleManager(sc);
			sc.setAttribute(ModuleManager.class.getName(),mm);
		}
		return mm;
	}

	private Module							defaultModule;
	/**
	 * Store all modules
	 */
	Map<String, Module>						modules;
	/**
	 * Map one module with a group of controllers
	 */
	private Map<String, List<IController>>	controllers;
	/**
	 * All moudule will process those controllers 
	 */
	private List<IController>				globalControllers;
	/**
	 * Map one module with all his ownered controllers 
	 */
	private Map<String, List<ControllerBean>>	controllersRef;

	ServletContext							servletContext;

	private List<Locale>					locales;

	private ModuleManager(ServletContext sc)
	{
		this.servletContext = sc;
		modules = new HashMap<String, Module>();
		globalControllers = new Vector<IController>();
		controllers = new HashMap<String, List<IController>>();
		controllersRef = new HashMap<String, List<ControllerBean>>();
		locales = new LinkedList<Locale>();
	}

	public List<Locale> getLocales()
	{
		return locales;
	}

	void sortLocales()
	{
		Collections.sort(locales,LocaleComparator.me());
	}

	public Locale getLocale(String localeName)
	{
		if (Weboo.DEFAULT_LOCALE_NAME.equals(localeName))
			return null;
		Iterator<Locale> it = locales.iterator();
		while (it.hasNext())
		{
			Locale l = it.next();
			if (null != l && l.toString().equals(localeName))
				return l;
		}
		return null;
	}

	void addLocales(List<Locale> ls)
	{
		if (null == ls)
			return;
		Iterator<Locale> it = ls.iterator();
		while (it.hasNext())
		{
			Locale l = it.next();
			if (ListUtils.findIndex(locales,l) >= 0)
				continue;
			locales.add(l);
		}
	}

	public Module getDefaultModule()
	{
		return defaultModule;
	}

	void setDefaultModule(Module defaultModule)
	{
		this.defaultModule = defaultModule;
	}

	public boolean hasModule(String moduleName)
	{
		return modules.containsKey(moduleName);
	}

	/**
	 * Install a module by a ModuleInstaller. This function will 
	 * try to register an module object indexed by module name.
	 * Then it will call installer.install() method to install
	 * the module.
	 * 
	 * @param loader
	 * @throws ModuleInstallException
	 * @throws ModuleOverlayException
	 * @throws InvalidModuleNameException 
	 * @throws ClassNotFoundException 
	 * @throws IllegalAccessException 
	 * @throws InstantiationException 
	 */
	public synchronized Module loadModule(ModuleLoader loader, ModuleContext context)
			throws ModuleOverlayException, InvalidModuleNameException, InstantiationException,
			IllegalAccessException, ClassNotFoundException
	{
		if (StringUtils.canBeClassName(loader.getName()))
		{
			throw new InvalidModuleNameException("\"" + loader.getName()
					+ "\" can not be an module name!");
		}
		if (hasModule(loader.getName()))
		{
			throw new ModuleOverlayException("Attempt to override module \"" + loader.getName()
					+ "\", please remove it at first!");
		}
		Module m = new Module();
		m.setBean(loader.getBean());
		processInstallModule(loader,m,context);
		processInstallControllers(loader,m);

		return m;
	}

	private Module processInstallModule(ModuleLoader loader, Module m, ModuleContext context)
	{
		context.setModule(m);
		context.setMessager(loader.getMessageFileName());
		if (log.isDebugEnabled())
			log.debug("merge locales: " + this.locales.size());
		addLocales(context.getLocales());
		if (log.isDebugEnabled())
			log.debug("done:" + this.locales.size());
		if (loader.getConfigFileName() != null)
			try
			{
				String myConfigName = loader.getConfigFileName();
				context.setConfig(context.nut().getConfig(myConfigName));
			}
			catch (Exception e)
			{
				context.setConfig(new Properties());
			}
		m.setModuleContext(context);
		m.setName(loader.getName());
		m.setInstaller(loader.getInstaller());
		m.setUninstaller(loader.getUninstaller());
		m.setComment(loader.getComment());
		m.setInputCharset(loader.getInputCharset());
		m.setOutputCharset(loader.getOuputCharset());
		context.setStyleHome(loader.getStyleHome());
		try
		{
			m.setActionManager(new ActionManager(context.getWebooConfig().pojo().iii(),loader
					.getActionHome(),this));
		}
		catch (Exception e)
		{
			log.warn("! " + m.getName() + ": Fail to set action manager > " + e.getMessage());
		}
		modules.put(loader.getName(),m);
		return m;
	}

	private void processInstallControllers(ModuleLoader loader, Module m)
			throws InstantiationException, IllegalAccessException, ClassNotFoundException
	{
		List<ControllerBean> beans = loader.getControllerList();
		if (null == beans || beans.size() == 0)
			return;

		Iterator<ControllerBean> it = beans.iterator();
		while (it.hasNext())
		{
			ControllerBean bean = it.next();
			IController c = (IController)Class.forName(bean.getType()).newInstance();
			if (null == bean.handleModuleNames() || bean.handleModuleNames().length == 0)
			{
				if (log.isDebugEnabled())
					log.debug("** Gloable IController:" + c.getClass().getName());
				globalControllers.add(c);
			}
			else
			{
				if (log.isDebugEnabled())
					log.debug("Module IController:" + c.getClass().getName());
				String[] names = bean.handleModuleNames();
				for (int i = 0; i < names.length; i++)
				{
					List<IController> list = controllers.get(names[i]);
					if (null == list)
					{
						list = new Vector<IController>();
						list.add(c);
						controllers.put(names[i],list);
					}
					else
					{
						if (!list.contains(c))
							list.add(c);
					}
				}
			}
		}
		controllersRef.put(m.getName(),beans);
	}

	public synchronized boolean uninstallModule(String moduleName) throws ModuleNotFoundException,
			ModuleUninstallException
	{
		try
		{
			List<ControllerBean> beans = controllersRef.remove(moduleName);
			if (null != beans)
			{
				Iterator it = beans.iterator();
				while (it.hasNext())
				{
					ControllerBean bean = (ControllerBean)it.next();
					if (null == bean.handleModuleNames() || bean.handleModuleNames().length == 0)
					{
						IController c = null;
						Iterator<IController> cit = globalControllers.iterator();
						while (cit.hasNext())
						{
							IController theC = cit.next();
							if (theC.getClass().getName().equals(bean.getType()))
							{
								c = theC;
								break;
							}
						}
						if (null != c)
							globalControllers.remove(c);
					}
					else
					{
						String[] names = bean.handleModuleNames();
						for (int i = 0; i < names.length; i++)
						{
							if (names[i].equals(moduleName))
							{
								controllers.remove(moduleName);
							}
							else if (controllers.containsKey(names[i]))
							{
								controllers.get(names[i]).remove(bean);
							}
						}
					}
				}
			}
			Module m = modules.remove(moduleName);
			return m.getUninstaller().uninstall();
		}
		catch (NullPointerException e)
		{
			throw new ModuleNotFoundException(e);
		}
	}

	public Module getModule(String moduleName) throws ModuleNotFoundException
	{
		Module m = modules.get(moduleName);
		if (null == m)
			throw new ModuleNotFoundException("Module \"" + moduleName + "\" not existed");
		return m;

	}

	public int size()
	{
		return modules.size();
	}

	public Set<String> names()
	{
		return modules.keySet();
	}

	public Collection<Module> modules()
	{
		return modules.values();
	}

	public List<IController> getControllers(String moduleName)
	{
		List<IController> list = new Vector<IController>(globalControllers);
		List<IController> cs = controllers.get(moduleName);
		if (cs != null && cs.size() > 0)
			list.addAll(cs);
		return list;
	}

	public List<ControllerBean> getControllerReferences(String moduleName)
	{
		return controllersRef.get(moduleName);
	}

	@Override
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append("<SERVER - " + this.getClass().getName() + ">");
		// Global controllers
		sb.append("\r\n  [GLOABLE CONTROLLERS]:");
		Iterator<IController> cit = globalControllers.iterator();
		while (cit.hasNext())
			sb.append("\r\n    @ " + cit.next().getClass().getName());

		// Modules
		sb.append("\r\n  [MODULES]:");
		Iterator<Module> it = modules.values().iterator();
		while (it.hasNext())
		{
			Module m = it.next();
			sb.append(m.getDescription());
		}
		sb.append("\r\n  [CONTROLLERS]:");
		Iterator<String> citKey = controllers.keySet().iterator();
		while (citKey.hasNext())
		{
			String key = citKey.next();
			sb.append("\r\n    - " + key);
			List<IController> list = controllers.get(key);
			Iterator<IController> l_it = list.iterator();
			while (l_it.hasNext())
				sb.append("\r\n        @ " + l_it.next().getClass().getName());
		}
		sb.append("\r\n  [CONTROLLERS-ref]:");
		citKey = controllersRef.keySet().iterator();
		while (citKey.hasNext())
		{
			String key = citKey.next();
			sb.append("\r\n    - " + key);
			Iterator<ControllerBean> cbit = controllersRef.get(key).iterator();
			while (cbit.hasNext())
			{
				ControllerBean cb = cbit.next();
				sb.append("\r\n        @ " + cb.getType() + " <-> " + cb.getModules());
			}
		}

		sb.append("\r\n</SERVER - " + this.getClass().getName() + ">");
		return sb.toString();
	}
}
